Skip to content

[Demo][Electron] Load prebuilt binaries at runtime#1546

Open
minggangw wants to merge 4 commits into
RobotWebTools:developfrom
minggangw:fix-1545
Open

[Demo][Electron] Load prebuilt binaries at runtime#1546
minggangw wants to merge 4 commits into
RobotWebTools:developfrom
minggangw:fix-1545

Conversation

@minggangw

@minggangw minggangw commented Jun 18, 2026

Copy link
Copy Markdown
Member

Summary

Make the Electron demos load the prebuilt rclnodejs native binary at runtime instead of recompiling it during electron-forge packaging, fix the turtle_tf2 demo's stuck loading screen on GPU-less environments (WSL2/headless), shorten the demo READMEs, and harden the native-loader unit tests.

Native loader (lib/native_loader.js)

  • Add mirrorPrebuiltToBuildRelease(): when an exact-match prebuilt binary is found, copy it into build/Release/rclnodejs.node so standard bindings resolution (and packaged Electron apps where ROS_DISTRO may be unset at runtime) can locate it. Non-destructive — never overwrites an existing build output, preserving local source builds.
  • Extract buildFromSource() and make it Electron-aware: when running under Electron, target Electron's headers/ABI via npm_config_target, npm_config_dist_url, and npm_config_arch so the compiled addon matches the runtime (the addon links libuv directly, so host runtime matters).
  • Replace the two duplicated inline npm run rebuild execSync calls (forced build + fallback build paths) with the shared buildFromSource() helper.

Electron demos — load prebuilt at packaging time

  • demo/electron/{car,manipulator,topics,turtle_tf2}/package.json: Remove the "rebuild": "electron-rebuild" script; add forge.rebuildConfig.ignoreModules: ["rclnodejs"] so electron-forge does not recompile rclnodejs, using the shipped prebuilt binary instead.

turtle_tf2 demo — WebGL / loading-screen fixes

  • main.js: append ignore-gpu-blocklist and enable-unsafe-swiftshader command-line switches so WebGL works on systems without a usable GPU (WSL2, headless, VMs) where Chromium blocklists hardware WebGL.
  • renderer.js: register ROS2 status listeners before 3D scene setup so the loading screen always reflects initialization progress, and wrap initializeScene()/setupEventListeners()/setupKeyboardControls() in a try/catch that surfaces a clear user-facing message ("Failed to initialize 3D view (WebGL unavailable): …") if scene init fails.

Documentation

  • Rewrite/shorten the four demo READMEs (car, manipulator, topics, turtle_tf2) — net ~1100 lines removed, kept accurate against each package.json (scripts, dependency versions, output directory names).

Tests (test/test-native-loader.js)

  • Update the two "exact match" prebuild tests to locate the prebuild candidate via existsSync.getCalls() (filtering for the prebuilds + rclnodejs.node path) instead of relying on existsSync.lastCall, which now points at the new build/Release mirror probe. Adds a guard assertion for a clearer failure message and drops the now-redundant rclnodejs.node check.

Fix: #1545

Copilot AI review requested due to automatic review settings June 18, 2026 07:13

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR improves the Electron demos’ ability to run using rclnodejs prebuilt binaries at runtime (instead of requiring electron-rebuild), and adds a couple of Electron-demo runtime UX/compatibility tweaks.

Changes:

  • Extend the native addon loader to (a) mirror a matched prebuilt binary into build/Release and (b) attempt Electron-targeted compilation settings when building from source under Electron.
  • Update Electron demo docs and Forge configs to stop using electron-rebuild and to exclude rclnodejs from Forge’s rebuild step.
  • Improve demo robustness by catching 3D scene init failures in the renderer and by enabling GPU/WebGL-related Electron flags in the TF2 demo.

Reviewed changes

Copilot reviewed 11 out of 11 changed files in this pull request and generated 4 comments.

Show a summary per file
File Description
lib/native_loader.js Adds mirroring of matched prebuilds to build/Release and introduces Electron-aware source build environment.
demo/electron/turtle_tf2/renderer.js Wraps 3D initialization in try/catch and updates the loading screen on failure.
demo/electron/turtle_tf2/README.md Updates setup/troubleshooting guidance to rely on runtime prebuilt selection instead of manual rebuild.
demo/electron/turtle_tf2/package.json Removes rebuild script and configures Forge rebuild to ignore rclnodejs.
demo/electron/turtle_tf2/main.js Adds Chromium command-line switches to improve WebGL availability on constrained systems.
demo/electron/topics/README.md Updates setup instructions to avoid electron-rebuild and rely on runtime prebuilt selection.
demo/electron/topics/package.json Removes rebuild script and configures Forge rebuild to ignore rclnodejs.
demo/electron/manipulator/README.md Updates setup guidance to source ROS 2 for prebuilt selection and removes rebuild instructions.
demo/electron/manipulator/package.json Removes rebuild script and configures Forge rebuild to ignore rclnodejs.
demo/electron/car/README.md Updates setup/troubleshooting to avoid manual rebuild and rely on prebuilt selection.
demo/electron/car/package.json Removes rebuild script and configures Forge rebuild to ignore rclnodejs.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread lib/native_loader.js
Comment on lines +63 to +71
if (detectPrebuildRuntime() === 'electron' && process.versions.electron) {
// node-gyp targets Electron via --target (Electron version) + --dist-url
// (Electron headers); it reads these from npm_config_* env vars.
env.npm_config_target = process.versions.electron;
env.npm_config_dist_url = 'https://electronjs.org/headers';
env.npm_config_arch = process.arch;
debug(`Building from source for Electron ${process.versions.electron}`);
} else {
debug('Building from source for Node.js');
Comment thread lib/native_loader.js
Comment on lines +37 to +44
// Copy a matched prebuilt binary into build/Release so that standard binary
// resolution (and packaged Electron apps where ROS_DISTRO may be unset at
// runtime) can still locate it. Non-destructive: never overwrites an existing
// build output, so local source builds are preserved.
function mirrorPrebuiltToBuildRelease(srcPath) {
try {
const destDir = path.join(__dirname, '..', 'build', 'Release');
const destPath = path.join(destDir, 'rclnodejs.node');
Comment on lines +70 to +76
const loadingText = loadingScreen.querySelector('div:last-child');
if (loadingText) {
loadingText.textContent =
'Failed to initialize 3D view (WebGL unavailable): ' + err.message;
loadingText.style.color = '#ff4444';
}
}
Comment on lines +16 to +21
// Allow WebGL to work on systems without a usable GPU (e.g. WSL2, headless,
// or VMs) where Chromium blocklists hardware WebGL. Without this, the 3D
// renderer fails to create a WebGL context and the visualization cannot start.
app.commandLine.appendSwitch('ignore-gpu-blocklist');
app.commandLine.appendSwitch('enable-unsafe-swiftshader');

@coveralls

coveralls commented Jun 18, 2026

Copy link
Copy Markdown

Coverage Status

coverage: 91.185% (-0.04%) from 91.22% — minggangw:fix-1545 into RobotWebTools:develop

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 12 out of 12 changed files in this pull request and generated 3 comments.

Comment thread lib/native_loader.js
Comment on lines +41 to +50
function mirrorPrebuiltToBuildRelease(srcPath) {
try {
const destDir = path.join(__dirname, '..', 'build', 'Release');
const destPath = path.join(destDir, 'rclnodejs.node');
if (fs.existsSync(destPath)) {
return;
}
fs.mkdirSync(destDir, { recursive: true });
fs.copyFileSync(srcPath, destPath);
debug(`Mirrored prebuilt binary to ${destPath}`);
Comment thread lib/native_loader.js
Comment on lines 116 to 129
try {
const candidate = getTaggedPrebuildFilename({
rosDistro,
ubuntuCodename,
arch,
runtime,
});
const candidatePath = path.join(prebuildDir, candidate);

if (fs.existsSync(candidatePath)) {
debug(`Found ${runtime} prebuilt binary: ${candidate}`);
mirrorPrebuiltToBuildRelease(candidatePath);
return require(candidatePath);
}
Comment thread lib/native_loader.js
Comment on lines +60 to +80
function buildFromSource() {
const env = { ...process.env };

if (detectPrebuildRuntime() === 'electron' && process.versions.electron) {
// node-gyp targets Electron via --target (Electron version) + --dist-url
// (Electron headers); it reads these from npm_config_* env vars.
env.npm_config_target = process.versions.electron;
env.npm_config_dist_url = 'https://electronjs.org/headers';
env.npm_config_arch = process.arch;
debug(`Building from source for Electron ${process.versions.electron}`);
} else {
debug('Building from source for Node.js');
}

childProcess.execSync('npm run rebuild', {
stdio: 'inherit',
cwd: path.join(__dirname, '..'),
timeout: 300000, // 5 minute timeout
env,
});
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[Demo][Electron] Load prebuilt binaries at runtime

3 participants